package org.fjsei.yewu.resolver.sei.inspect;

import com.querydsl.core.BooleanBuilder;
import graphql.relay.Connection;
import graphql.schema.DataFetchingEnvironment;
import md.cm.unit.Unit;
import md.cm.unit.Units;
import md.specialEqp.Equipments;
import md.specialEqp.ReportRepository;
import md.specialEqp.inspect.*;
import md.system.AuthorityRepository;
import md.system.User;
import md.system.UserRepository;
import org.fjsei.yewu.aop.MetricsLogger;
import org.fjsei.yewu.graphql.DbPageConnection;
import org.fjsei.yewu.input.IspCommonInput;
import org.fjsei.yewu.input.TaskInput;
import org.fjsei.yewu.input.WhereTree;
import org.fjsei.yewu.jpa.PageOffsetFirst;
import org.fjsei.yewu.resolver.Comngrql;
import org.fjsei.yewu.security.JwtUserDetailsService;
import org.fjsei.yewu.util.Tool;
import org.hibernate.Metamodel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.*;
import jakarta.persistence.metamodel.EntityType;
import java.util.List;
import java.util.Set;
import java.util.UUID;


//import org.springframework.data.jpa.repository.EntityGraph;   简名同名字冲突
//@Transactional类完全限定名：而不是jakarta.的那一个。
//import jakarta.transaction.Transactional;
//import java.sql.Date;


//实际相当于controller;
//这个类名字不能重复简明！
//graphQL安全性(query/mutation返回的对象可以进行id关联嵌套查询，如何控制关联信息访问)，这方面apollo做的较好：@注释扩展。
//信息安全私密字段不建议用graphQL的关联嵌套=内省查询，独立配合用REST接口也是候选方式。
//这里接口函数比graphqls模型多出了也没关系。

@Controller
@Transactional(readOnly = true)  //?查询60秒就超时。
public class IspMgrQuery extends Comngrql  {

    @Autowired
    private JwtUserDetailsService jwtUserDetailsService;
    @Autowired
    private Equipments eQPRepository;
    @Autowired
    private IspRepository iSPRepository;
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private TaskRepository taskRepository;
    @Autowired
    private ReportRepository reportRepository;
    @Autowired
    private Units units;
    @Autowired
    private AuthorityRepository authorityRepository;
    @Autowired private DetailRepository detailRepository;

    @PersistenceContext(unitName = "entityManagerFactorySei")
    private EntityManager emSei;

    //验证是否登录，谁登录，有那些角色的。只能复制或者上interface。
    //public User checkAuth() {   }报错，graphql重名!
    public Iterable<Isp> findAllISPs() {
        return iSPRepository.findAll();
    }

    public Iterable<Task> findAllTasks() {
        //task.isps的集合改动不会立刻体现，除非task自身有改动了。
        return taskRepository.findAll();
    }


    public Long countISP(UUID userId) {
        if (userId == null) return iSPRepository.count();
        User ispmen = userRepository.findById(userId).orElse(null);
        Assert.isTrue(ispmen != null,"未找到ispmen:"+ispmen);
        int myInt=iSPRepository.findByIspMen(ispmen).size();
        return Long.parseLong(new String().valueOf(myInt));
    }

     /**任务查询：分配任务和派工的前提列表查询。
     * ?从User/me.tasks[]#node().再去关联内省更好，没必要整出独立的graphQL查询接口。
     * 部门任务分配角色人:分给底下科室或者转移其它部门，科室任务分配角色人:分给底下任务责任人或者转移其它科室，责任人个人：确保任务不会超期要派工召集人员干活或者退回Task到科室中。
     *前端揭示：我部门任务， 我科室任务， 责任人关注任务：前端管的{状态{7个enum/or常见组合}过滤[]}+时间排序?,服务对象单位过滤,时间区间[]bsType entrust, 整个Task/IspUnit单位的所有任务全列表在后台维护{任务超期了}。
    * */
    @MetricsLogger
    @QueryMapping
    public Connection<Task> findAllTaskFilter(@Argument String orderBy, @Argument Boolean asc, @Argument TaskInput where, @Argument Integer first,
                                              @Argument String after, @Argument Integer last, @Argument String before, DataFetchingEnvironment env) {
        //排序是必须的！！还允许选择过滤字段。
        DbPageConnection<Task> connection=new DbPageConnection(env);
        //从Relay参数来反推出offset/limit;
        int offset=connection.getOffset();
        int limit=connection.getLimit();
        //【问题】这里排序date排序ASC后的，若没有添加id做辅助排序字段，导致多次执行从数据库读的分页数据很可能是重复的，CRDB返回实际记录的顺序不一定固定。不唯一的排序组合问题。
        //前端Relay列表看出：有些loadMore()可能并没有增加新实体，重复被自动归并了,导致队列长度也没增加；浪费通信数据。
        Pageable pageable;
        if (!StringUtils.hasLength(orderBy))
            pageable = PageOffsetFirst.of(offset, limit);
        else {
            pageable = PageOffsetFirst.of(offset, limit, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy, "id"));
        }
        QTask qm = QTask.task;
        BooleanBuilder builder = new BooleanBuilder();
        if(StringUtils.hasText(where.getDep()))
            builder.and(qm.dep.id.eq(Tool.fromGluuId(where.getDep()).getId()));
        if(StringUtils.hasText(where.getOffice()))
            builder.and(qm.office.id.eq(Tool.fromGluuId(where.getOffice()).getId()));
        if(null!=where.getDate1())
            builder.and(qm.date.goe(where.getDate1()));
        if(null!=where.getDate2())
            builder.and(qm.date.loe(where.getDate2()));
        if(null!=where.getStatusx() && where.getStatusx().size()>0)
            builder.and(qm.status.in(where.getStatusx()));
        if(null!=where.getEntrust())
            builder.and(qm.entrust.eq(where.getEntrust()));
        if(null!=where.getBsTypex() && where.getBsTypex().size()>0)
            builder.and(qm.bsType.in(where.getBsTypex()));
        if(StringUtils.hasText(where.getLiabler()))
            builder.and(qm.liabler.id.eq(Tool.fromGluuId(where.getLiabler()).getId()));
        if(StringUtils.hasText(where.getServu())) {
            Unit unit=fromInputUnitGlobalID(where.getServu());
            Assert.notNull(unit,"未找到Unit:"+where.getServu());
            builder.and(qm.servu.id.eq(unit.getId()));
        }
        Slice<Task> rpage= (Slice<Task>)taskRepository.findAll(builder,pageable);
        List<Task> list=(List<Task>) rpage.toList();
        //实际上SimpleListConnection也是DataFetcher<>的。内部直接提供简单cursor功能并且自己从env提取Relay参数。
        //SimpleListConnection()的输入必须是List必须是所有的node而且不能已经做了分页，
        // 给下面必须是完整Relay全部数据,SimpleListConnection自己提供游标和分页能力。
        return connection.setListData(list).get(env);
    }

    /*
    //最多1条正常状态的ISP， 唯一性保证： device/157/task/184 ;未派工的null task isp
    public Isp getISPofDevTask(Long dev, Long task) {
        List<Isp> allPage =iSPRepository.getByDev_IdAndTask_IdOrderByNextIspDate(dev,task);
        if(allPage.size()==0)     return null;
        else return allPage.get(0);
    }
    */

    /**查当前登录用户user正在关联关心的Isp
     * 直接给出参数字段去查询的独立接口Query模式。
     * QueryDsl：多层关联的内省有时会空指针问题;
     * 根据Isp.report主报告的stm流转状态机做过滤； #问题：iSPRepository历史报告中查找的？查询性能有问题？数据一致性+延时容忍性+搜索速度?IspEs模型？消息队列todoList?
     * 【zeebe引入】之后，这些需要全部改掉，不是依赖关系数据库的状态机流转表的搜索和状态过滤，今后改造：需要从流程引擎服务获取用户任务列表了。
    * */
    @Deprecated
    @QueryMapping
    public Connection<Isp> findMeIsps(@Argument String orderBy, @Argument Boolean asc, @Argument IspCommonInput where, @Argument Integer first, @Argument String after, @Argument Integer last, @Argument String before, DataFetchingEnvironment env) {
        User user= checkAuth();
        if(user==null)   return null;
        DbPageConnection<Isp> connection=new DbPageConnection(env);
        int offset=connection.getOffset();
        int limit=connection.getLimit();
        Pageable pageable;
        if (!StringUtils.hasLength(orderBy))
            pageable = PageOffsetFirst.of(offset, limit);
        else
            pageable = PageOffsetFirst.of(offset, limit, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy));
        QIsp qm = QIsp.isp;
        BooleanBuilder builder = new BooleanBuilder();
        BooleanBuilder userbuild = new BooleanBuilder();
        userbuild.or(qm.report.stm.master.eq(user)).or(qm.report.stm.authr.contains(user));
        BooleanBuilder stabuild = new BooleanBuilder();
        stabuild.or(qm.report.stm.sta.eq(Procedure_Enum.BEGIN)).or(qm.report.stm.sta.eq(Procedure_Enum.MAKE));
        BooleanBuilder detailBuild = new BooleanBuilder();
        detailBuild.or(qm.bus.isNotNull());
        builder.and(detailBuild).and(userbuild).and(stabuild);
/*        List<Isp>  isps = new ArrayList<Isp>();
        Iterable<Isp> rpage= iSPRepository.findAllNc(builder,pageable);
        rpage.forEach(item -> {
            isps.add(item);
        });*/
        Slice<Isp> rpage= (Slice<Isp>)iSPRepository.findAll(builder,pageable);
        List<Isp> isps=(List<Isp>) rpage.toList();
        return connection.setListData(isps).get(env);
    }


}



/*执行策略ExecutionStrategy都会导致lazy失败，这里读取已跨越DB的session延迟异步读取啊，
搞成了Entity和GraphQLResolver<TYPE>两个类独立分离了，还一样Lazy懒加载错误。
像@OneToMany这类JPA注解关联关系：对方类型必须是实体的。Object对象类型不能是接口interface的。
@MappedSuperclass注解的使用 Entity类之间的继承关系    https://blog.csdn.net/zty1317313805/article/details/80524900
使用懒加载时，报异常：session失效https://blog.csdn.net/wanping321/article/details/79532918
EntityManager不会立刻关闭，导致连接池连接数占用。高并发的系统最好不要使用OpenEntityManagerInView模式；https://blog.csdn.net/q1054261752/article/details/54773428
Spring动态替换Bean 接口=BeanPostProcessor； https://www.jianshu.com/p/853a081e4a02
若toPredicate内部应用：Subquery无法一次性选择多个字段出来做表达式比较条件!!，附加新建一个CriteriaQuery却又无法连接原始query报错无法serialize;
JPA关联嵌套子查询correlate subquery；而From()是不支持子查询的。 非关联子查询指子查询可以脱离主查询独立执行；关联子查询限制是子查询不能返回多于1行的数据.
*/


